from UserDict import UserDict

from web.db import sqlquote

from errors import ObjectDoesNotExist, MultipleResultError, IntegrityError, FieldError, FieldValueError


def fetch_object(resultset):
	resultset = iter(resultset)
	try:
		obj = next(resultset)
	except StopIteration:
		raise ObjectDoesNotExist('no object matching criteria')
	try:
		next(resultset)
		raise MultipleResultError('too many objects matching criteria')
	except StopIteration:
		return obj


def what(fields):
	return ','.join(fields) if fields else '*'


class Field(object):
	NOT_SET = object()

	def __init__(self, null=False, default=NOT_SET, unique=False):
		self.null = null
		self.default = default
		self.unique = False

	def __call__(self, value=NOT_SET):
		if value is None:
			if self.null:
				return value
			else:
				raise FieldValueError('field value must no be null')
		elif value == self.NOT_SET:
			if self.default == value:	
				raise FieldValueError('field value not set')
			return self.default
		else:
			return self._to_db(value)

	def _to_db(self, value):
		return value

	@property
	def ddl(self):
		items = [self._ddl_type()]
		if not self.null:
			items.append('NOT NULL')
		if self.default != self.NOT_SET:
			items.extend(('DEFAULT', str(sqlquote(self._ddl_default()))))
		if self.unique:
			items.append('UNIQUE')
		
		ddl_extra = self._ddl_extra()
		if ddl_extra:
			items.append(ddl_extra)
		
		return ' '.join(items)
	
	def _ddl_extra(self):
		pass

	def _ddl_type(self):
		raise NotImplementedError()

	def _ddl_default(self):
		raise NotImplementedError()


class StringField(Field):
	def __init__(self, maxlen=None, blank=False, **kwargs):
		super(StringField, self).__init__(**kwargs)
		self.maxlen = maxlen
		self.blank = blank

	def _to_db(self, value_):
		try:
			value = str(value_)
		except ValueError as error:
			raise FieldValueError(error)

		if self.maxlen and len(value) > self.maxlen:
			raise FieldValueError('field value length exceeds maximum', value=value_)

		if not len(value) and not self.blank:
			raise FieldValueError('field value must not be empty')

		return value

	def _ddl_type(self):
		return 'text' if self.maxlen is None else 'varchar({0})'.format(self.maxlen)

	def _ddl_default(self):
		return str(self.default)


class BoolField(Field):
	def _to_db(self, value):
		return int(bool(value))

	def _ddl_type(self):
		return 'integer'

	def _ddl_default(self):
		return 1 if bool(self.default) else 0


class DateTimeField(Field):
	def _ddl_type(self):
		return 'datetime'


class FieldSet(UserDict, object):
	@classmethod
	def get_ddl(cls):
		items = ['id integer primary key,']
		items.extend('"{}" {},'.format(name, field.ddl) for (name, field) in cls.iterfields())
		return '\n'.join(items)

	@classmethod
	def iterfields(cls):
		return (attr for attr in cls.__dict__.iteritems() if isinstance(attr[1], Field))

	def __setitem__(self, name, value):
		try:
			field = getattr(self, name)
		except AttributeError:
			raise FieldError('unknown field', name)

		if not isinstance(field, Field):
			raise FieldError('unknown field', name)
		
		try:
			value = field(value)
		except FieldValueError as error:
			error.name = name
			raise error

		super(FieldSet, self).__setitem__(name, value)

	def pop_fields(self, data, fields):
		for name in fields:
			self[name] = data.pop(name, self.get(name, Field.NOT_SET))
		return self

	
	def pop_all(self, data):
		fields = set()
		for name, field in self.iterfields():
			try:
				value = field(data.pop(name, Field.NOT_SET))
			except FieldValueError as error:
				error.name = name
				raise error
			
			super(FieldSet, self).__setitem__(name, value)
			
			fields.add(name)
		
		extra_fields = set(data.keys()) - fields
		if extra_fields:
			raise FieldError('unknown fields', extra_fields)
		
		return self
